import java.net.URL;
import java.io.*;
import java.security.*;
import javax.net.ssl.*;
import java.security.cert.X509Certificate;
import java.security.cert.CertificateException;

/**
 * Classe responsável pelo gerenciamento dos certificados
 */
public class CustomTrustManager implements X509TrustManager {

    // Nome da variável de ambiente que contem a senha
    // do arquivo de keystore
    private static final String SENHA_KEYSTORE_VAR = "javax.net.ssl.trustStorePassword";
    // Nome da variável de ambiente que contem o caminho
    // para o arquivo de keystore
    private static final String TRUST_STORE_VAR = "javax.net.ssl.trustStore";

    // O caminho para o arquivo de keystore
    private String keyStorePath;

    // A senha do arquivo de keystore
    private String senhaKeystore;

    // O objeto keystore que será carregado do arquivo
    private KeyStore keyStore;

    // O gerenciador padrão, que será utilizado pela nossa classe
    // evitando que precisemos reimplementar toda a
    // lógica de gerenciamento
    private X509TrustManager trustManager;

    public CustomTrustManager() throws Exception {
        super();

        // Carrega os certificados armazenados
        setKeyStorePath();

        // Inicia o gerenciador padrão
        getTrustManager();
    }

    // Método obrigatório para implementar a interface
    // do gerenciador
    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {

        // Utiliza o gerenciador padrão para executar a verificação
        trustManager.checkClientTrusted(chain, authType);
    }

    // Método obrigatório para implementar a interface
    // do gerenciador
    public void checkServerTrusted(X509Certificate[] chain,
    String authType) throws CertificateException {

        try {
            // Utiliza o gerenciador padrão para executar a verificação
            trustManager.checkClientTrusted(chain, authType);
        } catch (CertificateException e) {
            // No caso de erro, o certificado é importado e
            // a verificação é refeita
            adicionarCertificado(chain);
            trustManager.checkClientTrusted(chain, authType);
        }
    }

    // Método obrigatório para implementar a interface
    // do gerenciador
    public X509Certificate[] getAcceptedIssuers() {
        // Utiliza o gerenciador padrão recuperar os certificados
        return trustManager.getAcceptedIssuers();
    }

    // Método responsável por importar o certificado
    private void adicionarCertificado(X509Certificate[] chain) throws CertificateException {

        // É comum que o certificado contenha toda a
        // cadeia de certificação.
        // Nesse caso, toda ela será carregada
        for (X509Certificate cert : chain) {
            try {
                // Armazena o certificado com o alias Certificado_X_Y,
                // onde X é o serial e Y é o Current Time Millis.
                // Isso evita a duplicidade de alias.
                keyStore.setCertificateEntry("Certificado_"
                    + cert.getSerialNumber() + "_"
                    + System.currentTimeMillis(), cert);
            } catch (KeyStoreException e) {
                throw new CertificateException(
                    "Erro ao aceitar o certificado.", e);
            }
        }

        try {
            // Armazena os certificados no arquivo informado pela
            // variável de ambiente com a senha específica
            OutputStream streamKeyStore = new FileOutputStream( new File(keyStorePath));
            keyStore.store(streamKeyStore, senhaKeystore.toCharArray());
            getTrustManager();
        } catch (Exception e) {
            throw new CertificateException(
                "Erro ao aceitar o certificado.", e);
        }
    }

    // Método responsável por iniciar o
    // gerenciador de certificados
    protected void getTrustManager() throws Exception {
        // Recupera o algoritmo default de certificados
        String algoritmo = TrustManagerFactory.getDefaultAlgorithm();
        // Recupera a fabrica de gerenciadores
        // para o algoritmo padrão
        TrustManagerFactory fabricaTM = TrustManagerFactory.getInstance(algoritmo);

        // Inicia a fábrica com o keystore
        fabricaTM.init(this.keyStore);

        // Recupera todos os gerenciadores do sistema
        // para encontrar o responsável pelos certificados
        TrustManager tms[] = fabricaTM.getTrustManagers();
        for (TrustManager tm : tms) {
            if (tm instanceof X509TrustManager) {
                this.trustManager = (X509TrustManager) tm;
                return;
            }
        }

        // Caso não seja encontrado um gerenciador para
        //  os certificados, lança a exceção
        throw new NoSuchAlgorithmException(
            "TrustManager não encontrado!");
    }

    // Método responsável por carregar o keystore
    // do arquivo especificado
    protected void setKeyStorePath() throws Exception {
        // Recupera o caminho do arquivo e a senha do keystore
        this.keyStorePath = System.getProperty(TRUST_STORE_VAR);
        this.senhaKeystore = System.getProperty(SENHA_KEYSTORE_VAR);

        if (this.keyStorePath == null) {
            throw new KeyStoreException("O caminho do keystore " + " não pode ser null. Informe em " + TRUST_STORE_VAR);
        }

        // Obtem o keystore para o tipo padrão do sistema
        this.keyStore = KeyStore.getInstance( KeyStore.getDefaultType() );

        // Inicia o stream para leitura dos
        // certificados já importados
        // System.out.println(keyStorePath);
        InputStream streamKeyStore = new FileInputStream(new File(keyStorePath));

        try {
            // Carrega os certificados já importados utilizando
            // a senha informada
            this.keyStore.load(streamKeyStore, senhaKeystore.toCharArray());
        } finally {
            if (streamKeyStore != null) {
                streamKeyStore.close();
            }
        }
    }
}